//
//  Package sqs provides simple access towards the Amazon Simple Queue Service API
package sqs

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"encoding/xml"
	"launchpad.net/goamz/aws"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"
)

//  SQS holds the required information needed in order to communicate with the
//  Amazon Simple Queue Service API
type SQS struct {
	aws.Auth
	aws.Region
}

//  New creates a new instance of the SQS entity
//  
func New(a aws.Auth, r aws.Region) *SQS {
	return &SQS{a, r}
}

func (s *SQS) sign(h, m, p string, params map[string]string) {
	params["AWSAccessKeyId"] = s.AccessKey
	params["SignatureVersion"] = "2"
	params["SignatureMethod"] = "HmacSHA256"
	var sorted []string
	for k, v := range params {
		sorted = append(sorted, aws.Encode(k)+"="+aws.Encode(v))
	}
	sort.StringSlice(sorted).Sort()
	joined := strings.Join(sorted, "&")
	payload := m + "\n" + h + "\n" + p + "\n" + joined
	hash := hmac.New(sha256.New, []byte(s.SecretKey))
	hash.Write([]byte(payload))
	b64 := base64.StdEncoding
	sig := make([]byte, b64.EncodedLen(hash.Size()))
	b64.Encode(sig, hash.Sum(nil))
	params["Signature"] = string(sig)
}

func (s *SQS) query(u string, params map[string]string, resp interface{}) error {
	params["Version"] = "2012-11-05"
	params["Timestamp"] = time.Now().UTC().Format(time.RFC3339)
	endpoint, err := url.Parse(u)
	if err != nil {
		return err
	}
	s.sign(endpoint.Host, "GET", endpoint.RequestURI(), params)
	q := endpoint.Query()
	for k, v := range params {
		q.Add(k, v)
	}
	endpoint.RawQuery = q.Encode()
	r, err := http.Get(endpoint.String())
	if err != nil {
		return err
	}
	defer r.Body.Close()
	if r.StatusCode > 200 {
		return buildError(r)
	}
	return xml.NewDecoder(r.Body).Decode(resp)
}

func (s *SQS) AddPermission(n, l string, actions map[string]string) (*Response, error) {
    qUrl, err := s.GetQueueUrl(n)
    if err != nil {
        return nil, err
    }
    params := map[string]string{
        "Action": "AddPermission",
    }
    curAction := 1
    for k, v := range actions {
        //  k = AWS Account Id
        //  v = Action Name [* | SendMessage | ReceiveMessage | DeleteMessage | ChangeMessageVisibility | 
        //                   GetQueueAttributes | GetQueueUrl ]
        //  TODO:   Need to ensure that k is a valid, no hyphened, 12-digit AWS account number (Or, not?)
        curActionStr := string(curAction)
        params["AWSAccountId." + curActionStr] = k
        params["ActionName." + curActionStr] = v
        curAction++
    }
    resp := new(Response)
    err = s.query(qUrl.Queue.URL, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

func (s *SQS) ChangeMessageVisibility(n, h string, t int) (*Response, error) {
    qUrl, err := s.GetQueueUrl(n)
    if err != nil {
        return nil, err
    }
    params := map[string]string{
        "Action": "ChangeMessageVisibility",
        "ReceiptHandle": h,
        "VisibilityTimeout": string(t),
    }
    resp := new(Response)
    err = s.query(qUrl.Queue.URL, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

func (s *SQS) CreateQueue(n string, attribs map[string]string) (*CreateQueueResponse, error) {
    params := map[string]string{
        "Action": "CreateQueue",
        "QueueName": n,
    }
    attribNum := 1
    for key, val := range attribs {
        attribNumStr := string(attribNum)
        params["Attribute." + attribNumStr + ".Name"] = key
        params["Attribute." + attribNumStr + ".Value"] = val
        attribNum++
    }
    resp := new(CreateQueueResponse)
    err := s.query(s.SQSEndpoint, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

func (s *SQS) DeleteMessage(n, h string) (*Response, error) {
    qUrl, err := s.GetQueueUrl(n)
    if err != nil {
        return nil, err
    }
    params := map[string]string{
        "Action": "DeleteMessage",
        "ReceiptHandle": h,
    }
    resp := new(Response)
    err = s.query(qUrl.Queue.URL, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

func (s *SQS) DeleteQueue(n string) (*Response, error) {
    qUrl, err := s.GetQueueUrl(n)
    if err != nil {
        return nil, err
    }
    params := map[string]string{
        "Action": "DeleteQueue",
    }
    resp := new(Response)
    err = s.query(qUrl.Queue.URL, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

func (s *SQS) GetQueueAttributes(n string, attribs []string) (*GetQueueAttributesResponse, error) {
	qUrl, err := s.GetQueueUrl(n)
	if err != nil {
		return nil, err
	}
	params := map[string]string{
		"Action": "GetQueueAttributes",
	}
	aLen := len(attribs)
	if aLen > 0 {
		for i := 0; i < aLen; i++ {
			curNum := string(i + 1)
			params["AttributeName."+curNum] = attribs[i]
		}
	} else {
		params["AttributeName.1"] = "ALL"
	}
	resp := new(GetQueueAttributesResponse)
	err = s.query(qUrl.Queue.URL, params, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (s *SQS) GetQueueUrl(n string) (*GetQueueUrlResponse, error) {
	params := map[string]string{
		"Action":    "GetQueueUrl",
		"QueueName": n,
	}
	resp := new(GetQueueUrlResponse)
	err := s.query(s.SQSEndpoint, params, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (s *SQS) ListQueues(prefix string) (*ListQueuesResponse, error) {
	params := map[string]string{
		"Action":          "ListQueues",
		"QueueNamePrefix": prefix,
	}
	resp := new(ListQueuesResponse)
	err := s.query(s.SQSEndpoint, params, resp)
	if err != nil {
		return nil, err
	}
	return resp, nil
}

func (s *SQS) ReceiveMessage(n string, attribs []string, opts map[string]string) (*ReceiveMessageResponse, error) {
    qUrl, err := s.GetQueueUrl(n)
    if err != nil {
        return nil, err
    }
    params := map[string]string{
        "Action": "ReceiveMessage",
    }
    if len(attribs) == 1 {
        params["AttributeName"] = attribs[0]
    } else {
        attribNum := 1
        for _, a := range attribs {
            params["AttributeName." + string(attribNum)] = a
            attribNum++
        }
    }
    if opts != nil {
        for k, v := range opts {
            params[k] = v
        }
    }
    resp := new(ReceiveMessageResponse)
    err = s.query(qUrl.Queue.URL, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

func (s *SQS) RemovePermission(n, l string) (*Response, error) {
    qUrl, err := s.GetQueueUrl(n)
    if err != nil {
        return nil, err
    }
    params := map[string]string{
        "Action": "RemovePermission",
        "Label": l,
    }
    resp := new(Response)
    err = s.query(qUrl.Queue.URL, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

func (s *SQS) SendMessage(n string, b string, d int) (*SendMessageResponse, error) {
    qUrl, err := s.GetQueueUrl(n)
    if err != nil {
        return nil, err
    }
    params := map[string]string{
        "Action": "SendMessage",
        "MessageBody": b,
        "DelaySeconds": string(d),
    }
    resp := new(SendMessageResponse)
    err = s.query(qUrl.Queue.URL, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

func (s *SQS) SetQueueAttribute(n, a, v string) (*Response, error) {
    qUrl, err := s.GetQueueUrl(n)
    if err != nil {
        return nil, err
    }
    params := map[string]string{
        "Action": "SetQueueAttributes",
        "Attribute.Name": a,
        "Attribute.Value": v,
    }
    resp := new(Response)
    err = s.query(qUrl.Queue.URL, params, resp)
    if err != nil {
        return nil, err
    }
    return resp, nil
}

// vim: fdm=syntax fdn=1
